﻿using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using UMC.Net;

namespace UMC.Host
{
    abstract class WebSocket : IDisposable
    {
        public abstract void Write(byte[] buffer, int offset, int count);
        public abstract void Dispose();
    }

    class HttpsWebSocket : WebSocket
    {
        public byte[] buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(0x600);//new byte[0x600];
        System.IO.Stream stream;
        public HttpsWebSocket(HttpMime mime, System.IO.Stream stream)
        {
            mime1 = mime;
            this.stream = stream;
        }
        HttpMime mime1;
        async void WebSocketRead()
        {
            int size = 0;
            try
            {
                size = await stream.ReadAsync(buffer, 0, buffer.Length);

                if (size > 0)
                {
                    mime1.Write(buffer, 0, size);
                    WebSocketRead();
                }
                else
                {
                    this.Dispose();
                }
            }
            catch
            {
                this.Dispose();
            }
        }


        public override async void Write(byte[] buffer, int offset, int count)
        {
            await stream.WriteAsync(buffer, offset, count);

        }

        public override void Dispose()
        {
            mime1.Dispose();
            stream.Close();
            stream.Dispose();
            buffer = null;
            System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
        }
    }
    class HttpWebSocket : WebSocket
    {
        public byte[] buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(0x600);//new byte[0x600];
        Socket stream;
        public HttpWebSocket(HttpMime mime, Socket stream)
        {
            mime1 = mime;
            this.stream = stream;
            SocketAsyncEventArgs eventArgs = new SocketAsyncEventArgs();

            eventArgs.SetBuffer(buffer);
            eventArgs.Completed += Http;

            // this._IsBridging = true;
            // this.socket = socket;
            this.stream.ReceiveAsync(eventArgs);
        }
        HttpMime mime1;

        private void Http(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;
                case SocketAsyncOperation.Send:
                    // ProcessSend(e);
                    break;
                default:
                    break;
                    // throw new ArgumentException("The last operation completed on the socket was not a receive or send");
            }
        }
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                try
                {
                    this.mime1.Write(buffer, e.Offset, e.BytesTransferred);
                    if (!stream.ReceiveAsync(e))
                    {
                        ProcessReceive(e);
                    }
                }
                catch (Exception ex)
                {
                    mime1.OutText(500, ex.ToString());

                    this.Dispose();
                }
            }
            else
            {
                this.Dispose();

            }
        }



        public override void Write(byte[] buffer, int offset, int count)
        {
            stream.Send(buffer, offset, count, SocketFlags.None);

        }

        public override void Dispose()
        {
            mime1.Dispose();
            stream.Disconnect(true);
            // stream.d
            stream.Close();
            stream.Dispose();
            buffer = null;
            System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
        }
    }

    public class HttpMimeSocket : HttpMime
    {
        public override string Scheme => "http";

        public HttpMimeSocket(Socket stream, String host, String ip)
        {
            this._stream = stream;
            this.ActiveTime = UMC.Data.Utility.TimeSpan();

            this.pid = stream.GetHashCode();

            this._Host = host;

            this._remoteIpAddress = ip;

            HttpMimeServier.httpMimes.TryAdd(pid, this);
            SocketAsyncEventArgs eventArgs = new SocketAsyncEventArgs();

            eventArgs.SetBuffer(_data);
            eventArgs.Completed += Http;

            this._stream.ReceiveAsync(eventArgs);


        }
        Socket _stream;
        int pid = 0;
        public override int Id => pid;
        // public int Id => pid;
        String _remoteIpAddress, _Host;
        public override String Host => _Host;
        public override String RemoteIpAddress => _remoteIpAddress;

        public override void Write(byte[] buffer, int offset, int count)
        {
            if (isDispose == false)
            {
                try
                {

                    _stream.Send(buffer, offset, count, SocketFlags.None);
                }
                catch
                {
                    this.Dispose();
                }
            }
        }
        HttpMimeRequest request;
        private void Http(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;
                case SocketAsyncOperation.Send:
                    // ProcessSend(e);
                    break;
                default:
                    break;
                    // throw new ArgumentException("The last operation completed on the socket was not a receive or send");
            }
        }
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                this.ActiveTime = UMC.Data.Utility.TimeSpan();
                try
                {
                    if (request == null)
                    {
                        request = new HttpMimeRequest(this);
                    }
                    request.Receive(this._data, e.Offset, e.BytesTransferred);
                }
                catch (Exception ex)
                {
                    this.OutText(500, ex.ToString());

                    return;
                }
                if (!_stream.ReceiveAsync(e))
                {
                    ProcessReceive(e);
                }
            }
            else
            {
                this.Dispose();

            }
        }



        public override void OutputFinish()
        {
            try
            {
                request.Dispose();
                request = null;
                this.ActiveTime = UMC.Data.Utility.TimeSpan();
                this.TimeOut = 20;
            }
            catch
            {
                this.Dispose();
            }
        }
        public override void PrepareRespone(HttpMimeRequest httpMimeRequest)
        {
            this.TimeOut = 300;
            base.PrepareRespone(httpMimeRequest);
        }
        public override void Subscribe(HttpMimeRequest webRequest)
        {
            var _subscribe = UMC.Net.NetSubscribe.Subscribe(webRequest.Headers, webRequest.UserHostAddress, HttpMimeServier.Server, _stream, UMC.Data.WebResource.Instance().Provider["appSecret"]);
            if (_subscribe != null)
            {
                // HttpMimeSocket link;
                HttpMimeServier.httpMimes.TryRemove(pid, out var link);

                var writer = new Net.TextWriter(this.Write, _data);
                writer.Write($"HTTP/1.1 101 {HttpStatusDescription.Get(101)}\r\n");
                writer.Write("Connection: upgrade\r\n");
                writer.Write("Upgrade: websocket\r\n");
                writer.Write($"UMC-Publisher-Key: {HttpMimeServier.Server}\r\n");
                writer.Write("Server: UMC.Proxy\r\n\r\n");

                writer.Flush();
                writer.Dispose();
                _subscribe.Publish();
            }
            else
            {
                OutText(401, "连接验证不通过");
            }
        }
        protected override void WebSocket(NetContext context)
        {
            if (context.Tag is HttpWebRequest)
            {
                var webr = context.Tag as HttpWebRequest;
                this.WebSocket(webr);
            }
        }
        bool isDispose = false;
        public override void Dispose()
        {
            if (this._data != null)
            {
                System.Buffers.ArrayPool<byte>.Shared.Return(this._data);
            }
            if (isDispose == false)
            {
                isDispose = true;
                try
                {
                    _stream.Close();
                    _stream.Dispose();
                    _data = null;
                    _webSocket?.Dispose();
                }
                catch
                {

                }
            }
            HttpMimeServier.httpMimes.TryRemove(pid, out var _);

        }
        async void WebSocket(HttpWebRequest webRequest)
        {
            try
            {
                var url = webRequest.RequestUri;


                webRequest.Headers["Connection"] = "Upgrade";


                var client = new Socket(SocketType.Stream, ProtocolType.Tcp);

                await client.ConnectAsync(url.Host, url.Port);


                if (url.Scheme == "https")
                {
                    SslStream ssl = new SslStream(new NetworkStream(client, true), false, (sender, certificate, chain, sslPolicyErrors) => true);
                    await ssl.AuthenticateAsClientAsync(url.Host, null, SslProtocols.None, false);
                    var ws = new HttpsWebSocket(this, ssl);
                    await ssl.WriteAsync(ws.buffer, 0, UMC.Net.NetHttpResponse.Header(webRequest, ws.buffer));

                    _webSocket = ws;
                }
                else
                {
                    var ws = new HttpWebSocket(this, client);
                    client.Send(ws.buffer, 0, UMC.Net.NetHttpResponse.Header(webRequest, ws.buffer), SocketFlags.None);
                    _webSocket = ws;

                }

                HttpMimeServier.httpMimes.TryRemove(this.pid, out var _);
            }
            catch (Exception ex)
            {
                OutText(500, ex.ToString());
            }
        }
        WebSocket _webSocket;
        byte[] _data = System.Buffers.ArrayPool<byte>.Shared.Rent(0x600);
    }
}

